package com.redis.base.redis;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.util.SerializationUtils;

import com.redis.base.multiple.AbstractRedis;
import com.redis.base.utils.JsonUtil;

public abstract  class RedisImpl extends AbstractRedis implements IRedis {
	
	private static final Logger log = LoggerFactory.getLogger(RedisImpl.class);
	

	public abstract int getSpinNum();
	
	public abstract Long getExpire();
	
	public abstract boolean isEnable();
	
	public abstract int getSpinThreadTime();
	
	public abstract boolean isSpin();
	
	public abstract int getLockExpire();
	
	
	protected Integer getSpinNum(Integer num){
		if(getSpinNum() > 0){
			return getSpinNum();
		}
		
		return num;
	}
	

	/**
	 * 存数据到redis
	 * @param key
	 * @param value
	 */
	@Override
	public void put(final String key, final String value){
		put(key, value, getExpire());
	}
	
	@Override
	public void put(final String key, final String value,final Long  expire) {
		try {
			if(log.isDebugEnabled()) {
				log.debug("redis: method is put, key [" + key + "],cache_redis [" + isEnable() + "]");
			}
			if (!isEnable()) {
				return;
			}
			if(log.isDebugEnabled()) {
				log.debug("put redis====:" + key + ":" + value);
			}
			getOpsForValue().set(key, value, getRedisExpire(expire), TimeUnit.MILLISECONDS);		
		} catch (Exception e) {
			log.error(e.getMessage(),e);
		}
	}
	
	private Long getRedisExpire(Long  expire) {
		if(expire != null && expire > 0) {
			return expire;
		}
		return getExpire();
	}
	
	@Override
	public String get(final String key) {
		try {
			if(log.isDebugEnabled()) {
				log.debug("redis: method is get, key [" + key + "],cache_redis [" + isEnable() + "]");
			}
			if (!isEnable()) {
				return "";
			}
			return (String) getOpsForValue().get(key);
		} catch (Exception e) {
			log.error(e.getMessage(),e);
			return null;
		}
	}
	
	@Override
	public <T> T get(final String key, Class<T> objClass) {
		try {
			if(log.isDebugEnabled()) {
				log.debug("redis: method is get, key [" + key + "],cache_redis [" + isEnable() + "]");
			}
			if (!isEnable()) {
				return null;
			}
			return JsonUtil.json2Bean(getOpsForValue().get(key),objClass);
		} catch (Exception e) {
			log.error(e.getMessage(),e);
			return null;
		}
	}
	
	@Override
	public <T> List<T> getList(final String key, Class<T> objClass) {
		try {
			if(log.isDebugEnabled()) {
				log.debug("redis: method is get, key [" + key + "],cache_redis [" + isEnable() + "]");
			}
			if (!isEnable()) {
				return null;
			}
			return JsonUtil.json2List(String.valueOf(getOpsForValue().get(key)),objClass);
		} catch (Exception e) {
			log.error(e.getMessage(),e);
			return null;
		}
	}


	@Override
	public void put(final String key, final Object value){
		put(key, value, getExpire());
	}

	@Override
	public void put(final String key, final Object value,final Long expire) {
		try {
			if(log.isDebugEnabled()) {
				log.debug("redis: method is putObj, key [" + key + "],cache_redis [" + isEnable() + "]");
			}
			if (!isEnable()) {
				return;
			}
			getOpsForValue().set(key, JsonUtil.bean2Json(value), getRedisExpire(expire), TimeUnit.MILLISECONDS);
		} catch (Exception e) {
			log.error(e.getMessage(),e);
		}
	}
	
	
	@Override
	public void unLock(final String ... keys) {
		try {
			if(log.isDebugEnabled()) {
				log.debug("redis: method is release, key [" + keys + "],cache_redis [" + isEnable() + "]");
			}
			if (!isEnable()) {
				return;
			}
			redisTemplate().execute(new RedisCallback<Object>() {
				@Override
				public Object doInRedis(RedisConnection connection) {
					for(String key : keys){
						final String redisLockKey = getLockKey(key);
						connection.del(SerializationUtils.serialize(redisLockKey));
					}
					return true;
				}

			});
		} catch (Exception e) {
			log.error(e.getMessage(),e);
		}
	}
	
	/**
	 * 
	 * @Title: deleteSub
	 
	 * @return
	 * @return: Object
	 */
	@Override
	public Object deleteLike(final String ... keys) {
		try {
			if(log.isDebugEnabled()) {
				log.debug(
					"redis: method is deleteLike, key [" + keys + "],cache_redis [" + isEnable() + "]");
			}
			if (!isEnable()) {
				return 0;
			}
			return redisTemplate().execute(new RedisCallback<Object>() {
				@Override
				public Object doInRedis(RedisConnection connection) {
					List<String> keyList = new ArrayList<String>();
					Object result = null;
					if(keys == null || keys.length <=0){
						return null;
					}
					for(Object key : keys){
						Set<byte[]> keysTemp = connection.keys(("*"+String.valueOf(key) + "*").getBytes());
						for(byte[] by : keysTemp){
							result = connection.del(by);
							keyList.add("key:"+result+",");
						}
					}
					return result;

				}

			});
		} catch (Exception e) {
			log.error(e.getMessage(),e);
			return null;
		}
	}
	@Override
	public boolean delete(final String key) {
		try {
			if(log.isDebugEnabled()) {
				log.debug("redis: method is delete, key [" + key + "],cache_redis [" + isEnable() + "]");
			}
			if (!isEnable()) {
				return false;
			}
			redisTemplate().delete(key);
			return true;
		} catch (Exception e) {
			log.error(e.getMessage(),e);
			return false;
		}
	}
	
	@Override
	public boolean tryLock(String lockKey) {
		return lock(lockKey, isSpin(),getSpinNum(DEFAULT_SPIN_NUM));
	}
	
	
	@Override
	public boolean tryLock(String lockKey,boolean isSpin) {
		return lock(lockKey, isSpin,getSpinNum(DEFAULT_SPIN_NUM));
	}

	@Override
	public boolean tryLock(String lockKey,boolean isSpin,int spinNum) {
		return lock(lockKey, isSpin,getSpinNum(spinNum));
	}

	@Override
	public boolean lock(final String lockKey, final boolean isSpin,final int spinNum) {
		try {
			if(log.isDebugEnabled()) {
				log.debug("redis: method is acquire, key [" + lockKey + "],cache_redis [" + isEnable()
					+ "]");
			}
			if (!isEnable()) {
				return true;
			}
			final String redisLockKey = getLockKey(lockKey);
				return (Boolean) redisTemplate().execute(new RedisCallback<Object>() {
					@Override
					public Object doInRedis(RedisConnection connection) {
						try {
							if(log.isDebugEnabled()) {
								log.debug("Start acquiring a lock  {},thread {}.",redisLockKey,Thread.currentThread().getId());
							}
							long spinNumTemp = getSpinNum(DEFAULT_SPIN_NUM);
							while (spinNumTemp > 0) {
								if(log.isDebugEnabled()) {
									log.debug("start timeout:{},key:{} ",System.currentTimeMillis(),redisLockKey);
								}
								long  sysTime = System.currentTimeMillis();
								String expireTime = String.valueOf(sysTime + getLockExpire()+1);
								boolean flag = connection.setNX(SerializationUtils.serialize(redisLockKey),
										SerializationUtils.serialize(expireTime));
								if (flag) {
									if(log.isDebugEnabled()) {
										log.debug(" Manage to get a redis lock {},lockKey {} ", System.currentTimeMillis() ,redisLockKey);
									}
									return true;
								} 
						
					            
								String currentValueStr = String.valueOf(SerializationUtils.deserialize(connection.get(SerializationUtils.serialize(redisLockKey))));
							    if (currentValueStr != null && Long.parseLong(currentValueStr) < sysTime) {
							        // 锁已过期，获取上一个锁的过期时间，并设置现在锁的过期时间
							        String oldValueStr = String.valueOf(SerializationUtils.deserialize(connection.getSet(SerializationUtils.serialize(redisLockKey), SerializationUtils.serialize(expireTime))));
							        if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
							            // 考虑多线程并发的情况，只有一个线程的设置值和当前值相同，它才有权利加锁
							            return true;
							        }
							    }
								if (!isSpin) {// 如果不需要自旋获取锁，直接返回结果
									return false;
								}
	
								spinNumTemp -= 1;
								Thread.sleep(getSpinThreadTime());
							}
							log.info("stop timeout:{},key:{},thread:{} ",System.currentTimeMillis(),redisLockKey,Thread.currentThread().getId());
						} catch (Exception e) {
							log.error(e.getMessage(),e);
							return true;
						}
						log.info("Failed to get the lock {},thread {} ",redisLockKey,Thread.currentThread().getId());
						return false;
					}
				});
		} catch (Exception e) {
			log.error(e.getMessage(),e);
			return true;
		}
	}
	
	public ValueOperations<String, String> getOpsForValue(){
		return redisTemplate().opsForValue();
	}


	public RedisTemplate<String, String> redisTemplate() {
		return null;
	}

	@Override
	public long incr(String key) {
		try {
			return getOpsForValue().increment(key, 1);
		}catch(Exception e) {
			log.error(e.getMessage());
		}
		return 0;
	}
	

	@Override
	public Set<String> keys(String key) {
		return null;
	}

}
